#include <stdio.h>
#include <conio.h> // for _getch()
#include <windows.h>
#include <tlhelp32.h>
#include <tchar.h>
#include "lib/cJSON/cJSON.h"

#define WS_SUCCESS 0  // 成功返回值
#define WS_FAILURE (-1) // 失败返回值

#define WS_TRUE 1
#define WS_FALSE 0

#define PROJECT_XV6 "xv6"

#define MAX_APPS 10

typedef struct {
    char app_name[256];
    char app_arg[256];
    int attach_console;// 应用是否会依赖主程序的控制台。如果依赖，且应用程序被主程序打开，则关闭主程序时也会关闭该应用程序
} AppConfig;

typedef struct {
    char name[64];
    AppConfig apps[MAX_APPS];
    int app_count;
} ProjectConfig;

int lunch_app_at_dir(const char* typora_path, const char* workdir) {
    STARTUPINFOA si = { 0 };
    si.cb = sizeof(si);

    PROCESS_INFORMATION pi = { 0 };
    printf("Launching via cmd at dir: %s\n", workdir);

    // 构造命令行： cmd.exe /c start "" "<Typora路径>" "<工作目录>"
    char cmdline[1024];
    snprintf(cmdline, sizeof(cmdline),
        "cmd.exe /c start \"\" \"%s\" \"%s\"",
        typora_path, workdir);

    BOOL ok = CreateProcessA(
        NULL,            // 使用 cmdline 作为整个命令行
        cmdline,
        NULL, NULL,
        FALSE,
        CREATE_NO_WINDOW,   // 不显示 cmd 黑窗口
        NULL,
        NULL,
        &si,
        &pi);

    if (!ok) {
        DWORD dw = GetLastError();
        fprintf(stderr, "CreateProcess failed, code:%lu\n", dw);
        return WS_FAILURE;
    }

    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    return WS_SUCCESS;
}

int lunch_app(const char* app_name, const char* app_arg) {
    printf("lunch app %s %s\n", app_name, app_arg == NULL ? "" : app_arg);
    // 第三个参数是可执行程序名，第四个参数是命令行参数
    HINSTANCE ok = ShellExecuteA(
        NULL,               // 父窗口句柄
        "open",             // verb，"open" 表示打开
        app_name,     // 程序名，系统会在 PATH 和 System 目录里查找
        app_arg, // 参数
        NULL,               // 默认工作目录
        SW_SHOWNA       // 窗口显示方式
    );

    if ((INT_PTR)ok <= 32) {
        // 启动失败，返回值小于等于 32 表示错误代码
        return WS_FAILURE;
    }

    return WS_SUCCESS;
}

// 增强版：从注册表 App Paths 中查找程序
int find_exe_full_path(const char* exe_name, char* out_path, DWORD out_size) {
    // 1. 尝试从注册表 App Paths 中查找
    char reg_key[256];
    HKEY hkey;

    // 构造注册表键：exe_name + ".exe"
    snprintf(reg_key, sizeof(reg_key), "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\%s.exe", exe_name);

    if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, reg_key, 0, KEY_READ, &hkey) == ERROR_SUCCESS) {
        DWORD path_size = out_size;
        if (RegQueryValueExA(hkey, NULL, NULL, NULL, (LPBYTE)out_path, &path_size) == ERROR_SUCCESS) {
            RegCloseKey(hkey);
            return 1;
        }
        RegCloseKey(hkey);
    }

    // 2. 尝试用 SearchPath 搜索 PATH 环境变量
    if (SearchPathA(NULL, exe_name, ".exe", out_size, out_path, NULL) > 0
        || SearchPathA(NULL, exe_name, NULL, out_size, out_path, NULL) > 0) {
        return 1;
    }

    // 3. 如果还找不到，检查当前目录
    snprintf(out_path, out_size, "%s.exe", exe_name);
    if (GetFileAttributesA(out_path) != INVALID_FILE_ATTRIBUTES) {
        return 1;
    }

    return 0; // 全部失败
}

char* read_file(const char *filename) {
    FILE *file = NULL;
    long length = 0;
    char *content = NULL;
    size_t read_chars = 0;

    /* open in read binary mode */
    file = fopen(filename, "rb");
    if (file == NULL)
    {
        goto cleanup;
    }

    /* get the length */
    if (fseek(file, 0, SEEK_END) != 0)
    {
        goto cleanup;
    }
    length = ftell(file);
    if (length < 0)
    {
        goto cleanup;
    }
    if (fseek(file, 0, SEEK_SET) != 0)
    {
        goto cleanup;
    }

    /* allocate content buffer */
    content = (char*)malloc((size_t)length + sizeof(""));
    if (content == NULL)
    {
        goto cleanup;
    }

    /* read the file into memory */
    read_chars = fread(content, sizeof(char), (size_t)length, file);
    if ((long)read_chars != length)
    {
        free(content);
        content = NULL;
        goto cleanup;
    }
    content[read_chars] = '\0';


cleanup:
    if (file != NULL)
    {
        fclose(file);
    }

    return content;
}

static cJSON *parse_file(const char *filename)
{
    cJSON *parsed = NULL;
    char *content = read_file(filename);

    parsed = cJSON_Parse(content);

    if (content != NULL)
    {
        free(content);
    }

    return parsed;
}

int search_project(cJSON *root, const char* pname, ProjectConfig *out_project) {
    int ret = WS_FAILURE;
    cJSON *projects = cJSON_GetObjectItem(root, "projects");
    if (!cJSON_IsArray(projects)) {
        cJSON_Delete(root);
        return ret;
    }

    cJSON *project = NULL;
    cJSON_ArrayForEach(project, projects) {
        cJSON *name = cJSON_GetObjectItem(project, "name");
        if (cJSON_IsString(name) && strcmp(name->valuestring, pname) == 0) {
            strncpy(out_project->name, name->valuestring, sizeof(out_project->name));

            cJSON *apps = cJSON_GetObjectItem(project, "apps");
            if (!cJSON_IsArray(apps)) break;

            int app_index = 0;
            cJSON *app = NULL;
            cJSON_ArrayForEach(app, apps) {
                if (app_index >= MAX_APPS) break;

                cJSON *path = cJSON_GetObjectItem(app, "app_name");
                cJSON *args = cJSON_GetObjectItem(app, "app_arg");
                cJSON *attachconsole = cJSON_GetObjectItem(app, "attach_console");

                if (cJSON_IsString(path)) {
                    strncpy(out_project->apps[app_index].app_name, path->valuestring, sizeof(out_project->apps[app_index].app_name));
                }
                if (cJSON_IsString(args)) {
                    strncpy(out_project->apps[app_index].app_arg, args->valuestring, sizeof(out_project->apps[app_index].app_arg));
                }
                if (cJSON_IsNumber(attachconsole)) {
                    out_project->apps[app_index].attach_console = attachconsole->valueint;
                } else {
                    out_project->apps[app_index].attach_console = 0;
                }

                app_index++;
            }
            out_project->app_count = app_index;
            ret = WS_SUCCESS;
            break;
        }
    }

    cJSON_Delete(root);
    return ret; // 成功
}

int lunch_path_app(const char* app_name, const char* app_arg) {
    char cmdline[1024];
    snprintf(cmdline, sizeof(cmdline),
                 "%s %s", app_name, app_arg);
    printf("path cmd: %s\n", cmdline);
    int ret = system(cmdline);
    if (ret != 0) perror("path err");
    return ret;
}

void lunch_project(const ProjectConfig* pconf) {
    printf("lunch Project: %s\n", pconf->name);
    for (int i = 0; i < pconf->app_count; i ++) {
        if (pconf->apps[i].attach_console) {
            char fullpath[MAX_PATH];
            find_exe_full_path(pconf->apps[i].app_name, fullpath, sizeof(fullpath));
            lunch_app_at_dir(fullpath,  pconf->apps[i].app_arg);
        } else
            lunch_app(pconf->apps[i].app_name,  pconf->apps[i].app_arg);
    }
}

// 获取当前程序所在目录的完整路径
char* get_executable_dir() {
    char path[MAX_PATH];
    if (GetModuleFileNameA(NULL, path, MAX_PATH) == 0) {
        perror("GetModuleFileName failed");
        return NULL;
    }

    // 找到最后一个 '\\' 或 '/' 的位置
    char* last_slash = strrchr(path, '\\');
    if (last_slash == NULL) {
        last_slash = strrchr(path, '/');
    }

    if (last_slash != NULL) {
        *last_slash = '\0'; // 截断路径，只保留目录部分
    }

    return strdup(path); // 返回动态分配的字符串（需手动 free）
}

void open_project(const char* project_name) {
    // 获取程序所在目录
    char* exe_dir = get_executable_dir();
    if (exe_dir == NULL) {
        perror("Failed to get executable directory");
        return;
    }

    // 拼接 projects.json 的完整路径
    char json_path[MAX_PATH];
    snprintf(json_path, sizeof(json_path), "%s\\projects.json", exe_dir);
    free(exe_dir); // 释放动态分配的内存

    // 读取json
    cJSON* jcontent = parse_file(json_path);
    if (jcontent == NULL) {
        printf("failed to read %s\n", json_path);
        perror("failed reason");
        return;
    }
    // 检索项目
    ProjectConfig pconfig;
    int ret = search_project(jcontent, project_name, &pconfig);
    if (ret != WS_SUCCESS) {
        printf("Cannot find project: %s\n", project_name);
        return;
    }
    // print_project(&pconfig);

    // 启动软件
    lunch_project(&pconfig);
}

void remove_trailing_newline(char *str) {
    char *p = str + strlen(str) - 1;
    while (p >= str && (*p == '\n' || *p == '\r')) {
        *p-- = '\0';
    }
}

DWORD get_parent_pid(DWORD pid) {
    DWORD parent_pid = 0;
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (snapshot != INVALID_HANDLE_VALUE) {
        PROCESSENTRY32 pe32;
        pe32.dwSize = sizeof(PROCESSENTRY32);
        if (Process32First(snapshot, &pe32)) {
            do {
                if (pe32.th32ProcessID == pid) {
                    parent_pid = pe32.th32ParentProcessID;
                    break;
                }
            } while (Process32Next(snapshot, &pe32));
        }
        CloseHandle(snapshot);
    }
    return parent_pid;
}

BOOL is_launched_from_cmd() {
    DWORD current_pid = GetCurrentProcessId();
    DWORD parent_pid = get_parent_pid(current_pid);

    HANDLE hParent = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, parent_pid);
    if (hParent == NULL) {
        return FALSE;
    }

    CHAR parent_path[MAX_PATH];
    DWORD path_size = MAX_PATH;
    if (!QueryFullProcessImageNameA(hParent, 0, parent_path, &path_size)) {
        CloseHandle(hParent);
        return FALSE;
    }
    CloseHandle(hParent);

    // 检查父进程是否是 cmd.exe 或 powershell.exe
    const char* parent_name = strrchr(parent_path, '\\');
    if (parent_name == NULL) parent_name = parent_path;
    else parent_name++;

    return (_stricmp(parent_name, "cmd.exe") == 0) ||
           (_stricmp(parent_name, "powershell.exe") == 0) ||
           (_stricmp(parent_name, "bash.exe") == 0); // 适用于 WSL
}

// int main() {
//     char path[MAX_PATH];
//     if (find_exe_full_path("code", path, MAX_PATH)) {
//         printf("Found cmd: %s\n", path);
//         // LaunchCmdAtDir(path, NULL);
//     } else {
//         printf("cmd not found.\n");
//     }
// }

int main() {
    char input[256];
    printf("Input your project name: ");
    fgets(input, sizeof(input), stdin);
    remove_trailing_newline(input);
    printf("Your project is: %s\n", input);

    open_project(input);

    if (!is_launched_from_cmd()) {
        printf("\nPress any key to continue...");
        _getch();  // 等待用户按键，不显示按下的字符
    }
    return 0;
}
